﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using LitJson;

namespace LuaCheker
{
    public partial class Form1 : Form
    {
        private ContextMenuStrip contextMenu = new ContextMenuStrip();

        private int option = 1;
        private string lastPath = string.Empty;

        private List<string> ignoreList = new List<string>();
        private List<string> globalList = new List<string>();

        private Dictionary<string, string> fileMap = new Dictionary<string, string>();
        private Dictionary<string, string> outputMap = new Dictionary<string, string>();

        public Form1()
        {
            InitializeComponent();
            LoadSetting();
            LoadConfig();
            Initialize();
        }

        private void Initialize()
        {
            warningTreeView.LabelEdit = false;

            ToolStripMenuItem menuItem = new ToolStripMenuItem("添加到全局变量列表");
            menuItem.Click += new EventHandler(addGlobalMenuItem_Click);
            contextMenu.Items.Add(menuItem);

            menuItem = new ToolStripMenuItem("参看源文件");
            menuItem.Click += new EventHandler(viewFileMenuItem_Click);
            contextMenu.Items.Add(menuItem);

            switch (option)
            {
                case 0:
                    option1.Checked = false;
                    option3.Checked = false;
                    option5.Checked = false;
                    optionAll.Checked = true;
                    break;
                case 1:
                    option1.Checked = true;
                    option3.Checked = false;
                    option5.Checked = false;
                    optionAll.Checked = false;
                    break;
                case 3:
                    option1.Checked = false;
                    option3.Checked = true;
                    option5.Checked = false;
                    optionAll.Checked = false;
                    break;
                case 5:
                    option1.Checked = false;
                    option3.Checked = false;
                    option5.Checked = true;
                    optionAll.Checked = false;
                    break;
            }

            foreach (var g in globalList)
                gloablVarListBox.Items.Add(g);
        }

        private string GetIgnoreArgument()
        {
            string argument = " --ignore ";
            argument += string.Join(" --ignore ", ignoreList.ToArray());

            argument += " --globals ";
            argument += string.Join(" --globals ", globalList.ToArray());

            return argument;
        }

        private void LoadSetting()
        {
            if (!File.Exists(Directory.GetCurrentDirectory() + "/setting.json"))
            {
                option = 1;
                return;
            }

            string text = File.ReadAllText(Directory.GetCurrentDirectory() + "/setting.json");

            JsonData data = JsonMapper.ToObject(text);
            if (data == null)
            {
                toolStripStatusLabel1.Text = "配置文件加载错误！";
                return;
            }

            if (!data.Keys.Contains("option"))
            {
                toolStripStatusLabel1.Text = "配置文件加载错误！";
                return;
            }

            option = (int)data["option"];

            if (data.Keys.Contains("path"))
                lastPath = (string)data["path"];
            else
            {
                var path = Directory.GetCurrentDirectory();
                if (Directory.Exists(path + "/../Assets/ClientLogic/"))
                {
                    DirectoryInfo directoryInfo = new DirectoryInfo(path + "/../Assets/ClientLogic/");
                    lastPath = directoryInfo.FullName;
                }
                else
                    lastPath = path;
            }
        }

        private void SaveSetting()
        {
            JsonData data = new JsonData();
            data["option"] = option;
            data["path"] = lastPath;

            StreamWriter sw = new StreamWriter(Directory.GetCurrentDirectory() + "/setting.json");
            try
            {
                LitJson.JsonWriter jw = new JsonWriter(sw);
                jw.PrettyPrint = true;
                jw.IndentValue = 4;
                data.ToJson(jw);
            }
            catch (Exception)
            {

            }
            sw.Close();
        }

        private void LoadConfig()
        {
            string text = File.ReadAllText(Directory.GetCurrentDirectory() + "/config.json");

            JsonData data = JsonMapper.ToObject(text);
            if (data == null)
            {
                toolStripStatusLabel1.Text = "配置文件加载错误！";
                return;
            }

            if (!data.Keys.Contains("ignore") || !data.Keys.Contains("global"))
            {
                toolStripStatusLabel1.Text = "配置文件加载错误！";
                return;
            }

            ignoreList.Clear();
            globalList.Clear();

            var ignoreData = data["ignore"];
            int count = ignoreData.Count;
            for (int i = 0; i < count; ++i)
                ignoreList.Add((string)ignoreData[i]);

            var globalData = data["global"];
            count = globalData.Count;
            for (int i = 0; i < count; ++i)
                globalList.Add((string)globalData[i]);
        }

        private void SaveConfig()
        {
            JsonData data = new JsonData();

            JsonData ignoreData = new JsonData();
            data["ignore"] = ignoreData;

            foreach (var i in ignoreList)
                ignoreData.Add(i);

            JsonData globalData = new JsonData();
            data["global"] = globalData;

            foreach (var g in globalList)
                globalData.Add(g);

            StreamWriter sw = new StreamWriter(Directory.GetCurrentDirectory() + "/config.json");
            try
            {
                LitJson.JsonWriter jw = new JsonWriter(sw);
                jw.PrettyPrint = true;
                jw.IndentValue = 4;
                data.ToJson(jw);
            }
            catch (Exception)
            {

            }
            sw.Close();
        }

        private void CheckFile(string filePath, string fileName, string argument)
        {
            using (Process process = new Process())
            {
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.FileName = "luacheck.exe";
                process.StartInfo.CreateNoWindow = true;
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.Arguments = filePath + argument;
                process.Start();

                string key = filePath.Replace(lastPath, "");
                if (key.StartsWith("\\"))
                    key = key.Substring(1, key.Length - 1);

                string output = process.StandardOutput.ReadToEnd();
                outputMap[key] = output.TrimStart().Replace(filePath, fileName);
            }
        }

        private void CheckFiles()
        {
            string argument = GetIgnoreArgument();
            int count = fileMap.Count;

            int i = 0;
            Task[] tasks = new Task[Math.Min(count, 5)];

            var iterator = fileMap.GetEnumerator();
            while (iterator.MoveNext())
            {
                var file = iterator.Current.Key;
                var fileName = iterator.Current.Value;

                tasks[i++] = Task.Run(() =>
                {
                    CheckFile(file, fileName, argument);
                });

                if (i == tasks.Length)
                {
                    try
                    {
                        Task.WaitAll(tasks);

                        count -= i;
                        if (count != 0)
                        {
                            tasks = new Task[Math.Min(count, 5)];
                            i = 0;
                        }
                    }
                    catch (AggregateException)
                    {
                    }
                }
            }

            UpdateUI(string.Empty);
        }

        private List<int> GetUnUsedLineNumbers(string output)
        {
            List<int> numbers = new List<int>(10);

            var regex = new Regex(@"(.lua:)(?<line>\d+)(:)");
            var warnings = output.Split('\n');

            int length = warnings.Length;
            for (int i = 0; i < length; ++i)
            {
                var line = warnings[i];
                if (!line.Contains("unused variable ") || !regex.IsMatch(line))
                    continue;

                var groups = regex.Match(line).Groups;
                int number = int.Parse(groups["line"].Value);

                // 最多检查前100行
                if (number >= 100)
                    continue;

                numbers.Add(number);
            }

            return numbers;
        }

        private void RemoveUnUsedLines(string filePath, List<int> unusedLines)
        {
            string path = lastPath + "\\" + filePath;
            if (!File.Exists(path))
                return;

            bool bHasUnUsedLine = false;

            var lines = File.ReadAllLines(path, Encoding.UTF8);
            for (int i = 0; i < lines.Length; ++i)
            {
                var line = lines[i];

                if (unusedLines.IndexOf(i + 1) != -1 && line.Contains("require"))
                {
                    bHasUnUsedLine = true;
                    break;
                }
            }

            if (!bHasUnUsedLine)
                return;

            UTF8Encoding utf8WithoutBom = new UTF8Encoding(false);
            StreamWriter writer = new StreamWriter(path, false, utf8WithoutBom);

            for (int i = 0; i < lines.Length; ++i)
            {
                var line = lines[i];

                if (unusedLines.IndexOf(i + 1) != -1 && line.Contains("require"))
                    continue;

                writer.WriteLine(line);
            }

            writer.Flush();
            writer.Close();
        }


        private void UpdateUI(string filter)
        {
            warningTreeView.BeginUpdate();
            fileListBox.BeginUpdate();

            warningTreeView.Nodes.Clear();
            fileListBox.Items.Clear();

            var iterator = outputMap.GetEnumerator();
            while (iterator.MoveNext())
            {
                var fileName = iterator.Current.Key;
                var output = iterator.Current.Value;

                TreeNode node = null;
                var nodes = warningTreeView.Nodes.Find(fileName, false);
                if (nodes != null && nodes.Length > 0)
                {
                    node = nodes[0];
                    node.Nodes.Clear();
                }
                else
                {
                    node = new TreeNode();
                    node.Text = fileName;
                    warningTreeView.Nodes.Add(node);
                }


                string[] lines = output.Split('\n');

                int count = 0;
                foreach (var line in lines)
                {
                    if (string.IsNullOrEmpty(line) || !line.StartsWith(" "))
                        continue;

                    if (!string.IsNullOrEmpty(filter) && !line.Contains(filter))
                        continue;

                    TreeNode subNode = new TreeNode();
                    subNode.Text = line;

                    ++count;
                    node.Nodes.Add(subNode);
                }

                if (count == 0)
                    warningTreeView.Nodes.Remove(node);

                fileListBox.Items.Add(fileName);
            }

            warningTreeView.EndUpdate();
            fileListBox.EndUpdate();
        }

        private void openDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
        {
            using (FolderBrowserDialog dialog = new FolderBrowserDialog())
            {
                dialog.SelectedPath = lastPath;
                if (dialog.ShowDialog(this) != DialogResult.OK)
                    return;

                var directory = dialog.SelectedPath;
                if (!Directory.Exists(directory))
                    return;

                string argument = GetIgnoreArgument();

                var files = Directory.GetFiles(directory, "*.lua", SearchOption.AllDirectories);
                if (files == null || files.Length == 0)
                    return;

                fileMap.Clear();
                outputMap.Clear();

                foreach (var file in files)
                {
                    if (!file.EndsWith(".lua"))
                        continue;

                    var fileInfo = new FileInfo(file);
                    if (option != 0 && fileInfo.LastWriteTime.AddDays(option + 1) < DateTime.Now)
                        continue;

                    fileMap.Add(file, fileInfo.Name);
                }

                lastPath = directory;
                SaveSetting();

                CheckFiles();
                UpdateUI(string.Empty);
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            var filterText = filterTextBox.Text;
            UpdateUI(filterText);
        }

        private void button3_Click(object sender, EventArgs e)
        {
            UpdateUI("accessing undef");
        }

        private void button2_Click(object sender, EventArgs e)
        {
            CheckFiles();
        }

        private void warningTreeView_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            if (e.Button != MouseButtons.Right)
                return;

            if (e.Node == null)
                return;

            if (e.Node.Level == 0)
                return;

            warningTreeView.SelectedNode = e.Node;
            contextMenu.Show(warningTreeView, e.X, e.Y);
        }

        private void warningTreeView_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            if (e.Button != MouseButtons.Left)
                return;

            if (e.Node == null)
                return;

            if (e.Node.Level != 0)
                return;

            var path = lastPath + "\\" + e.Node.Text;
            if (!File.Exists(path))
                return;

            using (Process process = new Process())
            {
                var fileInfo = new FileInfo(path);
                //var directoryInfo = fileInfo.Directory;

                process.StartInfo.UseShellExecute = false;
                process.StartInfo.FileName = "explorer.exe";
                process.StartInfo.CreateNoWindow = true;
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.Arguments = "/select," + fileInfo.FullName;
                process.Start();
                process.WaitForExit();
            }
        }

        private void addGlobalMenuItem_Click(object sender, EventArgs e)
        {
            var node = warningTreeView.SelectedNode;
            if (node == null)
                return;

            var prefix = "accessing undefined variable '";
            var text = node.Text;

            int index = text.IndexOf(prefix);
            if (index == -1)
                return;

            index += prefix.Length;
            var wv = text.Substring(index, text.Length - index - 2);

            if (globalList.IndexOf(wv) != -1)
                return;

            globalList.Add(wv);
            gloablVarListBox.Items.Add(wv);

            SaveConfig();
        }

        private void viewFileMenuItem_Click(object sender, EventArgs e)
        {
            var node = warningTreeView.SelectedNode;
            if (node == null || node.Parent == null)
                return;

            var path = lastPath + "\\" + node.Parent.Text;
            if (!File.Exists(path))
                return;

            var regex = new Regex(@"(.lua:)(?<line>\d+)(:)");

            var text = node.Text;
            if (!regex.IsMatch(text))
                return;

            var groups = regex.Match(text).Groups;
            int number = int.Parse(groups["line"].Value);

            using (Process process = new Process())
            {
                var fileInfo = new FileInfo(path);

                process.StartInfo.UseShellExecute = false;
                process.StartInfo.FileName = "code.cmd";
                process.StartInfo.CreateNoWindow = true;
                process.StartInfo.UseShellExecute = true;
                process.StartInfo.Arguments = "-g " + fileInfo.FullName + ":" + number;
                process.Start();
                process.WaitForExit();
            }
        }

        private void option1_Click(object sender, EventArgs e)
        {
            option1.Checked = true;
            option3.Checked = false;
            option5.Checked = false;
            optionAll.Checked = false;
            option = 1;
            SaveSetting();
        }

        private void option3_Click(object sender, EventArgs e)
        {
            option1.Checked = false;
            option3.Checked = true;
            option5.Checked = false;
            optionAll.Checked = false;
            option = 3;
            SaveSetting();
        }

        private void option5_Click(object sender, EventArgs e)
        {
            option1.Checked = false;
            option3.Checked = false;
            option5.Checked = true;
            optionAll.Checked = false;
            option = 5;
            SaveSetting();
        }

        private void optionAll_Click(object sender, EventArgs e)
        {
            option1.Checked = false;
            option3.Checked = false;
            option5.Checked = false;
            optionAll.Checked = true;
            option = 0;
            SaveSetting();
        }

        private void button4_Click(object sender, EventArgs e)
        {
            warningTreeView.ExpandAll();
        }

        private void button5_Click(object sender, EventArgs e)
        {
            warningTreeView.CollapseAll();
        }

        private void button6_Click(object sender, EventArgs e)
        {
            UpdateUI("unused variable ");
        }

        private void button7_Click(object sender, EventArgs e)
        {
            toolStripStatusLabel1.Text = "开始删除!";

            var iterator = outputMap.GetEnumerator();
            while (iterator.MoveNext())
            {
                var numbers = GetUnUsedLineNumbers(iterator.Current.Value);
                if (numbers.Count > 0)
                    RemoveUnUsedLines(iterator.Current.Key, numbers);
            }

            toolStripStatusLabel1.Text = "删除完成！";
        }
    }
}
